package xyz.scootaloo.code.common

import kotlin.math.max

/**
 * @author flutterdash@qq.com
 * @since 2021/10/9 19:37
 */

interface TextTableBuilder {
    fun append(array: List<Any>, header: Boolean = false)
    fun clear()
    fun printOnConsole(buff: StringBuilder = StringBuilder())
}

class TextTable private constructor() : TextTableBuilder {

    override fun append(array: List<Any>, header: Boolean) {
        if (header)
            this.header = array.copy()
        else
            this.body.add(array)
    }

    override fun clear() = body.clear()
    override fun printOnConsole(buff: StringBuilder) = buildTable(buff).log()

    private var onlyAsciiCode = true
    private var header: List<Any> = emptyList()
    private val body: MutableList<List<Any>> = ArrayList()

    private fun buildTable(buff: StringBuilder): StringBuilder {
        val lens = calcLenOfColumn()
        val splitLine = buildSplitLine(lens)

        buff.append(splitLine).append('\n')
        if (header.isNotEmpty()) {
            buildArray(buff, lens, header).append('\n')
            buff.append(splitLine).append('\n')
        }

        for (row in body) {
            buildArray(buff, lens, row).append('\n')
        }

        if (body.isNotEmpty())
            buff.append(splitLine).append('\n')

        return buff
    }

    private fun calcLenOfColumn(): IntArray {
        fun checkAndSet(text: String): Int {
            val realLength = stringLen(text)
            if (realLength != text.length && !onlyAsciiCode)
                onlyAsciiCode = false
            return realLength
        }

        fun getLenOfArray(): Int {
            val headerLen = header.size
            var bodyLen = 0
            for (row in body) {
                if (row.size > bodyLen)
                    bodyLen = row.size
            }
            return max(headerLen, bodyLen)
        }

        fun calcLen(lens: IntArray, row: List<Any>) {
            for (i in lens.indices) {
                if (i < row.size)
                    lens[i] = max(lens[i], checkAndSet(row[i].toString()))
            }
        }

        val lens = IntArray(getLenOfArray()) { 0 }
        calcLen(lens, header)
        for (row in body)
            calcLen(lens, row)

        return lens
    }

    private fun buildSplitLine(lens: IntArray): String {
        val buff = StringBuilder()
        buff.append('+')
        for (i in lens.indices) {
            buff.appendChars('-', lens[i] + 2)
            buff.append('+')
        }
        return buff.toString()
    }

    private fun buildArray(buff: StringBuilder, lens: IntArray, row: List<Any>): StringBuilder {
        buff.append('|')
        for (col in row.indices) {
            val colWidth = lens[col]
            if (col >= row.size) {
                buff.append(' ', colWidth + 2).append('|')
            } else {
                buff.append(' ')
                buildCell(buff, colWidth, row[col].toString())
                buff.append(' ').append('|')
            }
        }
        return buff
    }

    private fun buildCell(buff: StringBuilder, width: Int, text: String) {
        val realWidth = if (onlyAsciiCode) text.length else stringLen(text)
        val diff = width - realWidth
        if (realWidth <= width) {
            buff.append(text)
            buff.appendChars(' ', diff)
        } else {
            if (width <= 3) {
                buff.append('.', width)
            } else {
                for (pos in 0 until (width - 3))
                    buff.append(text[pos])
                buff.appendChars('.', 3)
            }
        }
    }

    private fun stringLen(text: String?): Int {
        if (text == null) return 0
        var len = text.length
        for (c in text) {
            val intValue = c.code
            // 中文的在 unicode 编码中的区间
            if (intValue in 19968..40869) {
                len += 1
            }
        }
        return len
    }

    private fun StringBuilder.appendChars(symbol: Char, count: Int): StringBuilder {
        if (count == 0) return this
        for (i in 0 until count)
            this.append(symbol)
        return this
    }

    companion object {
        fun get(): TextTableBuilder = TextTable()
    }
}

fun main() {
    val builder = TextTable.get()
    builder.append(listOf("class", "name", "id"), true)
    builder.append(listOf("121221", "21211", "121", "23232"))
    builder.append(listOf("121221", "21211"))
    builder.printOnConsole()
}
